{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### defaultdict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `defaultdict` is a specialized dictionary found in the `collections` module. (It is a subclass of the `dict` type)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Standard dictionaries in Python will raise an exception if we try to access a non-existent key:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'a'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-3-4ff0d9af6f7a>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0md\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'a'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m: 'a'"
     ]
    }
   ],
   "source": [
    "d['a']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we can certainly use the `.get` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "NoneType"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result = d.get('a')\n",
    "type(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can even specify a default value for the key if it is not present:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d.get('a', 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Often we have dictionaries where we want to return a consistent default value if the requested key does not exist.\n",
    "\n",
    "Although we can do so using the `.get` method as above, we have to remember to use the same default value every time - plus it gets a little cumbersome.\n",
    "\n",
    "Let's say we want to keep track of the number of occurrences of individual characters in a string.\n",
    "\n",
    "We might approach it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "counts = {}\n",
    "sentence = \"able was I ere I saw elba\"\n",
    "\n",
    "for c in sentence:\n",
    "    if c in counts:\n",
    "        counts[c] += 1\n",
    "    else:\n",
    "        counts[c] = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'a': 4, 'b': 2, 'l': 2, 'e': 4, ' ': 6, 'w': 2, 's': 2, 'I': 2, 'r': 1}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So this works, but we have that `if` statement - it would be nice to simplify our code somewhat:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "counts = {}\n",
    "for c in sentence:\n",
    "    counts[c] = counts.get(c, 0) + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'a': 4, 'b': 2, 'l': 2, 'e': 4, ' ': 6, 'w': 2, 's': 2, 'I': 2, 'r': 1}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, that works well and is much cleaner. But if we have to specify that default value (`0` in this case) many times in our code when working with the same dictionary, we have to remember what the default needs to be each time.\n",
    "\n",
    "Instead, we could use a `defaultdict`. In a `defaultdict` we specify what the default value is for a missing key - more precisely, we specify a default factory method that is called:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "counts = defaultdict(lambda : 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "for c in sentence:\n",
    "    counts[c] += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(<function __main__.<lambda>()>,\n",
       "            {'a': 4,\n",
       "             'b': 2,\n",
       "             'l': 2,\n",
       "             'e': 4,\n",
       "             ' ': 6,\n",
       "             'w': 2,\n",
       "             's': 2,\n",
       "             'I': 2,\n",
       "             'r': 1})"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see that simplified our code quite a bit, but the result is not quite a dictionary - it is a `defaultdict`. However, it inherits from `dict` so all the dictionary methods we have grown to know and love are still available because ` defaultdict` **is** a `dict`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(counts, defaultdict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(counts, dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And `counts` behaves like a regular dictionary too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_items([('a', 4), ('b', 2), ('l', 2), ('e', 4), (' ', 6), ('w', 2), ('s', 2), ('I', 2), ('r', 1)])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts.items()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts['a']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The main difference is when we request a non-existent key:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts['python']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get the default value back - not only that, but it actually created that key as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(<function __main__.<lambda>()>,\n",
       "            {'a': 4,\n",
       "             'b': 2,\n",
       "             'l': 2,\n",
       "             'e': 4,\n",
       "             ' ': 6,\n",
       "             'w': 2,\n",
       "             's': 2,\n",
       "             'I': 2,\n",
       "             'r': 1,\n",
       "             'python': 0})"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So this is a bit different from using `.get`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And of course we can manipulate our dictionary just like a standard dictionary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(<function __main__.<lambda>()>,\n",
       "            {'a': 4,\n",
       "             'b': 2,\n",
       "             'l': 2,\n",
       "             'e': 4,\n",
       "             ' ': 6,\n",
       "             'w': 2,\n",
       "             's': 2,\n",
       "             'I': 2,\n",
       "             'r': 1,\n",
       "             'python': 0,\n",
       "             'hello': 'world'})"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counts['hello'] = 'world'\n",
    "counts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(<function __main__.<lambda>()>,\n",
       "            {'a': 4,\n",
       "             'b': 2,\n",
       "             'l': 2,\n",
       "             'e': 4,\n",
       "             ' ': 6,\n",
       "             'w': 2,\n",
       "             's': 2,\n",
       "             'I': 2,\n",
       "             'r': 1,\n",
       "             'python': 0})"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "del counts['hello']\n",
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Very often you will see what looks like a **type** specified as the default factory - but keep in mind that it is in fact the corresponding functions (constructors) that are actually being specified.\n",
    "\n",
    "For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bool()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "''"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = defaultdict(int)\n",
    "d['a']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = defaultdict(bool)\n",
    "d['a']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "''"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = defaultdict(str)\n",
    "d['a']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = defaultdict(list)\n",
    "d['a']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that this no different than writing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = defaultdict(lambda: list())\n",
    "d['a']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a look at another example of where a `defaultdict` can be useful.\n",
    "\n",
    "Suppose we have a dictionary structure that has people's names as keys, and a dictionary for the value that contains the person's eye color. We want to create a dictionary of eye colors, with a list of the people's names that have that eye color:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "persons = {\n",
    "    'john': {'age': 20, 'eye_color': 'blue'},\n",
    "    'jack': {'age': 25, 'eye_color': 'brown'},\n",
    "    'jill': {'age': 22, 'eye_color': 'blue'},\n",
    "    'eric': {'age': 35},\n",
    "    'michael': {'age': 27}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What we want is a dictionary with the eye colors (and `unknown` as the key if the eye color was not specified), and the names of the people with that eye color.\n",
    "\n",
    "Let's first do this without a `defaultdict`, and also not using `.get`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "eye_colors = {}\n",
    "for person, details in persons.items():\n",
    "    if 'eye_color' in details:\n",
    "        color = details['eye_color']\n",
    "    else:\n",
    "        color = 'unknown'\n",
    "    if color in eye_colors:\n",
    "        eye_colors[color].append(person)\n",
    "    else:\n",
    "        eye_colors[color] = [person]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'blue': ['john', 'jill'], 'brown': ['jack'], 'unknown': ['eric', 'michael']}"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eye_colors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's simplify this by leveraging the `.get` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "eye_colors = {}\n",
    "for person, details in persons.items():\n",
    "    color = details.get('eye_color', 'Unknown')\n",
    "    person_list = eye_colors.get(color, [])\n",
    "    person_list.append(person)\n",
    "    eye_colors[color] = person_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'blue': ['john', 'jill'], 'brown': ['jack'], 'Unknown': ['eric', 'michael']}"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eye_colors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And finally let's use a `defaultdict`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "eye_colors = defaultdict(list)\n",
    "for person, details in persons.items():\n",
    "    color = details.get('eye_color', 'Unknown')\n",
    "    eye_colors[color].append(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(list,\n",
       "            {'blue': ['john', 'jill'],\n",
       "             'brown': ['jack'],\n",
       "             'Unknown': ['eric', 'michael']})"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eye_colors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we create a `defaultdict` we have to specify the factory method as the first argument, but thereafter we can specify key/value pairs just like we would with the `dict` constructor (they are basically just passed along to the underlying `dict`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = defaultdict(bool, k1=True, k2=False, k3='python')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(bool, {'k1': True, 'k2': False, 'k3': 'python'})"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, using this, if we had used a `defaultdict` for the Person values, we could simplify our previous example a bit more:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "persons = {\n",
    "    'john': defaultdict(lambda: 'unknown', \n",
    "                        age=20, eye_color='blue'),\n",
    "    'jack': defaultdict(lambda: 'unknown',\n",
    "                        age=20, eye_color='brown'),\n",
    "    'jill': defaultdict(lambda: 'unknown',\n",
    "                        age=22, eye_color='blue'),\n",
    "    'eric': defaultdict(lambda: 'unknown', age=35),\n",
    "    'michael': defaultdict(lambda: 'unknown', age=27)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "eye_colors = defaultdict(list)\n",
    "for person, details in persons.items():\n",
    "    eye_colors[details['eye_color']].append(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(list,\n",
       "            {'blue': ['john', 'jill'],\n",
       "             'brown': ['jack'],\n",
       "             'unknown': ['eric', 'michael']})"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eye_colors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It was a little tedious defining that `defaultdict` for every instance in our `persons` dictionary.\n",
    "\n",
    "This is a good example of where a **partial** function would be really useful. (I cover partial functions in Part 1 of this series, or you can review the documentation here: https://docs.python.org/3.7/library/functools.html#functools.partial\n",
    "\n",
    "(You can also just use a lambda function as well)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import partial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "eyedict = partial(defaultdict, lambda: 'unknown')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively we could also just define it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "eyedict = lambda *args, **kwargs: defaultdict(lambda: 'unknown', *args, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "persons = {\n",
    "    'john': eyedict(age=20, eye_color='blue'),\n",
    "    'jack': eyedict(age=20, eye_color='brown'),\n",
    "    'jill': eyedict(age=22, eye_color='blue'),\n",
    "    'eric': eyedict(age=35),\n",
    "    'michael': eyedict(age=27)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'john': defaultdict(<function __main__.<lambda>.<locals>.<lambda>()>,\n",
       "             {'age': 20, 'eye_color': 'blue'}),\n",
       " 'jack': defaultdict(<function __main__.<lambda>.<locals>.<lambda>()>,\n",
       "             {'age': 20, 'eye_color': 'brown'}),\n",
       " 'jill': defaultdict(<function __main__.<lambda>.<locals>.<lambda>()>,\n",
       "             {'age': 22, 'eye_color': 'blue'}),\n",
       " 'eric': defaultdict(<function __main__.<lambda>.<locals>.<lambda>()>,\n",
       "             {'age': 35}),\n",
       " 'michael': defaultdict(<function __main__.<lambda>.<locals>.<lambda>()>,\n",
       "             {'age': 27})}"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "persons"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can use our previous code just as before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "eye_colors = defaultdict(list)\n",
    "for person, details in persons.items():\n",
    "    eye_colors[details['eye_color']].append(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(list,\n",
       "            {'blue': ['john', 'jill'],\n",
       "             'brown': ['jack'],\n",
       "             'unknown': ['eric', 'michael']})"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eye_colors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at another example where we use a non-deterministic factory. We could make a database call, an API call, and so on. To keep this simple I'm going to use the current time as my default.\n",
    "\n",
    "In this example we want to keep track of how many times certain functions are being called, as well as when they were **first** called. To do this I want to be able to decorate the functions I want to keep track of, and I want to be able to specify the dictionary that should be used so I can keep a reference to it so I can examine the results.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict, namedtuple\n",
    "from datetime import datetime\n",
    "from functools import wraps\n",
    "\n",
    "def function_stats():\n",
    "    d = defaultdict(lambda: {\"count\": 0, \"first_called\": datetime.utcnow()})\n",
    "    Stats = namedtuple('Stats', 'decorator data')\n",
    "    \n",
    "    def decorator(fn):\n",
    "        @wraps(fn)\n",
    "        def wrapper(*args, **kwargs):\n",
    "            d[fn.__name__]['count'] += 1\n",
    "            return fn(*args, **kwargs)\n",
    "        return wrapper\n",
    "    \n",
    "    return Stats(decorator, d)        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "stats = function_stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{}"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict(stats.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "@stats.decorator\n",
    "def func_1():\n",
    "    pass\n",
    "\n",
    "@stats.decorator\n",
    "def func_2(x, y):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{}"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict(stats.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "func_1()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'func_1': {'count': 1,\n",
       "  'first_called': datetime.datetime(2018, 12, 29, 22, 43, 48, 828143)}}"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict(stats.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "func_1()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'func_1': {'count': 2,\n",
       "  'first_called': datetime.datetime(2018, 12, 29, 22, 43, 48, 828143)}}"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict(stats.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "func_2(10, 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'func_1': {'count': 2,\n",
       "  'first_called': datetime.datetime(2018, 12, 29, 22, 43, 48, 828143)},\n",
       " 'func_2': {'count': 1,\n",
       "  'first_called': datetime.datetime(2018, 12, 29, 22, 43, 49, 714090)}}"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict(stats.data)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
